<?php
/**
 * account.php
 * ==============================================
 * Copy right 2015-2021  by https://www.tianmtech.com/
 * ----------------------------------------------
 * This is not a free software, without any authorization is not allowed to use and spread.
 * ==============================================
 * @desc : 账单
 * @author: zhanglinxiao<zhanglinxiao@tianmtech.cn>
 * @date: 2021/12/10
 * @version: v1.0.0
 * @since: 2021/12/10 09:11
 */

namespace App\Repositories;

use App\Exceptions\BasicException;
use App\Models\MainDB\AccountCalculateLog;
use App\Models\MainDB\AccountEarningsLog;
use App\Models\MainDB\AccountMoneyLog;
use App\Models\MainDB\User;
use App\Models\MainDB\UserAccountChangeType;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;

class AccountRepository
{
    /**
     * 日志uuid
     * @var string
     */
    private $logUuid = "";

    /**
     * @var Logger
     */
    private $logInstance;


    /**
     * 资金变动
     * @param int $userId 用户ID
     * @param int $changeType 变动类型，详见：ln_user_account_change_type
     * @param float $changeMoney 变更金币，最多支持两位小数
     * @param string $changeDesc 变更文字描述
     * @param int $sourceId 来源ID
     * @param int $roomId 直播间ID（非必传）
     * @return array
     * @throws BasicException
     */
    public function change($userId, $changeType, $changeMoney, $changeDesc, $sourceId, $roomId = 0) {
        //记录时间
        $startTime = microtime(true);

        //先写入文件日志
        $this->saveFileLog("change.start", compact(
            "userId", "changeType", "changeMoney",
            "changeDesc", "sourceId", "roomId"
        ));

        //账单日志ID
        $accountLogId = 0;

        try {

            //获取用户余额信息
            $balanceInfo = $this->getUserBalance($userId);

            //文件日志打印余额
            $this->saveFileLog("change.balance", $balanceInfo);


            //获取变更类型列表
            $changeTypeList = $this->getChangeTypeList();

            if (empty($changeTypeList[$changeType])) {
                Throw new BasicException(10010, "类型不合法");
            }

            if ($changeMoney <= 0) {
                Throw new BasicException(10010, "金币数值不合法");
            }


            //变动类型信息
            $changeTypeInfo = $changeTypeList[$changeType];

            //账户变动类型：1-增加；0-减少；
            $symbol = $changeTypeInfo['symbol'];
            //是否是收益：0-不是收益；1-是收益；
            $isEarning = $changeTypeInfo['is_earning'];
            //是否立即计算：0-延迟计算；1-立即计算；
            $isImmediately = $changeTypeInfo['is_immediately'];

            //减少金币，则检查余额是否充足
            if ($symbol == 0) {
                if ($isEarning == 0) {
                    if (!empty($balanceInfo['money']) && $balanceInfo['money'] < $changeMoney) {
                        Throw new BasicException(10010, '余额金币不足');
                    }
                } else {
                    if (!empty($balanceInfo['earnings']) && $balanceInfo['earnings'] < $changeMoney) {
                        Throw new BasicException(10010, '收益金币不足');
                    }
                }
            }

            //添加账单日志
            $accountLogId = $this->addAccountLog(array(
                'user_id' => $userId,
                'change_money' => $changeMoney,
                'change_desc' => $changeDesc,
                'change_type' => $changeType,
                'source_id' => $sourceId,
                'room_id' => $roomId,
            ), $isEarning);

            if (!empty($accountLogId)) {
//                if ($isImmediately == 1 || $symbol == 0) {
                //管理后台调用频次不高，全部走实时
                if (true) {

                    //实时执行账单变动
                    $changeResult = $this->changeImmediately($symbol, $isEarning, $userId, $changeMoney);
                    if (empty($changeResult)) {
                        //日志删除（以防外层未嵌套事务，没有把日志信息回滚）
                        $this->delAccountLog($accountLogId, $isEarning);
                        Throw new BasicException(10010, "操作余额失败，请检查账户余额是否充足");
                    } else {


                        /**
                         * 添加账单计算变动日志
                         */
                        $changeMoneyV2 = 0;
                        $changeEarningsV2 = 0;

                        if ($isEarning == 0) {
                            if ($symbol == 1) {
                                $changeMoneyV2 = $changeMoney;
                            } else {
                                $changeMoneyV2 = $changeMoney * -1;
                            }
                        } else {
                            if ($symbol == 1) {
                                $changeEarningsV2 = $changeMoney;
                            } else {
                                $changeEarningsV2 = $changeMoney * -1;
                            }
                        }
                        $newBalanceInfo = $this->getUserBalance($userId);
                        $this->addCalculateLog($userId, $accountLogId, $changeMoneyV2, $changeEarningsV2, $balanceInfo, $newBalanceInfo);
                        //收益变动时，可提现收益也需要变动
                        $this->changeUserWithdrawableEarnings($userId, $changeMoney, $symbol, $changeType, $isEarning, $newBalanceInfo);
                    }

                } else {
//                    //异步执行
//                    $changeResult = $this->changeByQueue($userId, $changeType, $changeMoney, $symbol, $isEarning, $accountLogId);
//                    if (empty($changeResult)) {
//                        Throw new BasicException(10010, "账户余额更新失败");
//                    }
                }
            } else {
                Throw new BasicException(10010, '添加账单日志失败');
            }
            //结束
            $this->saveFileLog("change.success");
        } catch (\Exception $e) {

            $error = $e->getMessage();

        }


        $endTime = microtime(true);
        //记录执行入账方法消耗的时间
        $constDuration = $endTime - $startTime;
        $this->saveFileLog("change.time：共耗时：{$constDuration}秒");

        if (!empty($error)) {
            //返回错误
            if (isset($accountLogId) && isset($isEarning)) {
                //日志删除（以防外层未嵌套事务，没有把日志信息回滚）
                $this->delAccountLog($accountLogId, $isEarning);
            }

            $this->saveFileLog("change.fail：{$error}");

            Throw new BasicException(10010, $error);
//            return array(
//                "result" => false,
//                "error" => $error,
//            );
        } else {
            return array(
                "result" => true,
                "error" => '',
                "account_log_id" => $accountLogId,
            );
        }


    }

    /**
     * 获取日志uuid
     * @return string
     */
    private function getLogUuid() {
        if (empty($this->logUuid)) {
            $this->logUuid = uniqid() . rand(1000, 9999);
        }
        return $this->logUuid;
    }

    /**
     * 清除历史日志痕迹（队列执行时用到）
     */
    private function clearLog() {
        $this->logUuid = "";
        $this->logInstance = null;
    }


    /**
     * 获取日志实例对象
     * @return \Monolog\Logger
     */
    private function getLogInstance() {
        try {
            if (!isset($this->logInstance)) {
                $logPath = storage_path() . "/logs/account/" . date('Ymd') . '.log';
                $handler = new StreamHandler($logPath, Logger::DEBUG, true, 0777);
                $log = new Logger("account");
                $log->pushHandler($handler);

                $this->logInstance = $log;
            }
        } catch (\Exception $e) {

        }

        return $this->logInstance;
    }

    /**
     * 账单文件日志
     * @param $msg
     * @param array $context_arr
     * @return null
     */
    private function saveFileLog($msg, $context_arr = array()) {
        $logUuid = $this->getLogUuid();
        $logInstance = $this->getLogInstance();

        if (!empty($logInstance)) {
            $logInstance->info("===uuid {$logUuid}=== " . $msg, $context_arr);
        }
    }


    /**
     * 获取变更类型列表
     * @return array
     */
    private function getChangeTypeList() {
        $list = array();

        //redis缓存时间
        $minute = 1;

        $key = "qh:account:types";
        //redis获取数据
        $typeList = Cache::get($key);
        if (!empty($typeList)) {
            $typeList = json_decode($typeList, true);
            if (!empty($typeList)) {
                $list = $typeList;
            }
        } else {
            /**@var UserAccountChangeType $userAccountChangeTypeModel */
            $userAccountChangeTypeModel = app("model_user_account_change_type");
            $list = $userAccountChangeTypeModel->newQuery()
                ->select(array('id', 'name', 'symbol', 'is_earning', 'is_immediately'))
                ->get()
                ->keyBy('id')
                ->toArray();;
            Cache::add($key, json_encode($list), $minute);
        }
        return $list;
    }

    /**
     * 获取余额和收益
     * @param $userId
     * @return array
     */
    public function getUserBalance($userId) {
        $money = 0;
        $earnings = 0;

        /**@var User $userModel */
        $userModel = app("model_user");
        $info = $userModel->newQuery()
            ->select(array('money', 'earnings'))
            ->where('user_id', $userId)
            ->first();

        if (!empty($info)) {
            $info = $info->toArray();
        }
        if (!empty($info['money'])) {
            $money = $info['money'];
        }

        if (!empty($info['earnings'])) {
            $earnings = $info['earnings'];
        }

        return compact("money", "earnings");
    }


    /**
     * 添加账单日志
     * @param $isEarning
     * @param $data
     * @return int
     */
    private function addAccountLog($data, $isEarning) {
        $data['created_at'] = date("Y-m-d H:i:s");
        if ($isEarning == 0) {//余额
            /**@var AccountMoneyLog $accountMoneyLogModel */
            $accountMoneyLogModel = app("model_account_money_log");
            $logId = $accountMoneyLogModel->newQuery()->insertGetId($data);
        } else {//收益
            /**@var AccountEarningsLog $accountEarningsLogModel */
            $accountEarningsLogModel = app("model_account_earnings_log");
            $logId = $accountEarningsLogModel->newQuery()->insertGetId($data);
        }

        return $logId;
    }


    /**
     * 删除账单日志
     * @param $accountLogId
     * @param $isEarning
     */
    private function delAccountLog($accountLogId, $isEarning) {
        if ($isEarning == 0) {//余额
            /**@var AccountMoneyLog $accountMoneyLogModel */
            $accountMoneyLogModel = app("model_account_money_log");
            $accountMoneyLogModel->newQuery()->where('id', $accountLogId)->forceDelete();
        } else {//收益
            /**@var AccountEarningsLog $accountEarningsLogModel */
            $accountEarningsLogModel = app("model_account_earnings_log");
            $accountEarningsLogModel->newQuery()->where('id', $accountLogId)->forceDelete();
        }
    }


    /**
     * 添加账单计算变动日志
     * @param $userId
     * @param $accountLogIds
     * @param $changeMoney
     * @param $changeEarnings
     * @param $oldBalance
     * @param $newBalance
     */
    public function addCalculateLog($userId, $accountLogIds, $changeMoney, $changeEarnings, $oldBalance, $newBalance) {

        if (is_array($accountLogIds)) {
            $accountLogIds = implode(',', $accountLogIds);
        }

        /**@var AccountCalculateLog $AccountCalculateLogModel */
        $AccountCalculateLogModel = app("model_account_calculate_log");


        $calculateLogData = array(
            'user_id' => $userId,
            'account_log_ids' => $accountLogIds,
            'old_money' => $oldBalance['money'],
            'change_money' => $changeMoney,
            'new_money' => $newBalance['money'],
            'old_earnings' => $oldBalance['earnings'],
            'change_earnings' => $changeEarnings,
            'new_earnings' => $newBalance['earnings'],
        );

        $AccountCalculateLogModel->newQuery()->insertGetId($calculateLogData);

    }


    /**
     * 实时执行账单变动
     * @param $symbol
     * @param $isEarning
     * @param $userId
     * @param $changeMoney
     * @return bool
     */
    private function changeImmediately($symbol, $isEarning, $userId, $changeMoney) {
        /**@var User $userModel */
        $userModel = app("model_user");
        if ($isEarning == 0) {

            if ($symbol == 1) {//增加
                $changeResult = $userModel->newQuery()
                    ->where('user_id', $userId)
                    ->update(array(
                        'money' => DB::raw("money + {$changeMoney}"
                        )));
            } else {//减少

                $changeResult = $query = $userModel->newQuery()
                    ->where('user_id', $userId)
                    ->where('money', '>=', $changeMoney)
                    ->update(array(
                        'money' => DB::raw("money - {$changeMoney}"
                        )));
            }

        } else {
            if ($symbol == 1) {//增加
                $changeResult = $userModel->newQuery()
                    ->where('user_id', $userId)
                    ->update(array(
                        'earnings' => DB::raw("earnings + {$changeMoney}"
                        )));
            } else {//减少

                $changeResult = $query = $userModel->newQuery()
                    ->where('user_id', $userId)
                    ->where('earnings', '>=', $changeMoney)
                    ->update(array(
                        'earnings' => DB::raw("earnings - {$changeMoney}"
                        )));
            }
        }


        if (!empty($changeResult)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 收益变动时，可提现收益也需要变动
     * @param $userId
     * @param $changeMoney
     * @param $symbol
     * @param $changeType
     * @param $isEarning
     * @param $newBalanceInfo
     */
    public function changeUserWithdrawableEarnings($userId, $changeMoney, $symbol, $changeType, $isEarning, $newBalanceInfo)
    {
        /**@var UserAssetRepo $userAssetRepo */
        $userAssetRepo = app("repo_user_asset");
        if (in_array($changeType, array(23, 24))) {
            $userAssetRepo->changeUserWithdrawableEarnings($userId, $changeMoney, $symbol);
        } elseif ($isEarning == 1) {
            $earningWithdrawable = (float)($userAssetRepo->getUserWithdrawableEarnings($userId));
            if ($earningWithdrawable != $newBalanceInfo['earnings']) {
                $userAssetRepo->changeUserWithdrawableEarnings($userId, abs($earningWithdrawable - $newBalanceInfo['earnings']), $symbol);
            }
        }
    }
}
